﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.Xaml.Tools.XamlDom;
using System.Xaml;
using System.Xaml.Schema;
using System.Diagnostics;
using System.Reflection;

namespace BindingFinder
{
    public static class XamlDomUtilities
    {
        public static string GetMemberValueOfTypeProperty(this XamlDomMember memberNode)
        {
            object value = null;

            if (memberNode != null)
            {
                XamlDomValue domValue = memberNode.Item as XamlDomValue;
                if (domValue != null)
                {
                    value = domValue.Value;
                }
                else
                {
                    XamlDomObject domObject = memberNode.Item as XamlDomObject;
                    if (domObject != null)
                    {
                        value = GetPositionalParameter(domObject, 0);
                        value = ((XamlDomValue)value).Value;
                    }
                }
            }
            return (string)value;

        }
        public static string GetMemberValueOfTypeProperty(this XamlDomObject objectNode, string memberName)
        {
            if (objectNode != null)
            {
                XamlDomMember memberNode = objectNode.GetMemberNode(memberName);
                return memberNode.GetMemberValueOfTypeProperty();
            }
            else
            {
                return string.Empty;
            }
            
        }
        public static string GetValue(XamlDomItem item)
        {
            string value = ((string)((XamlDomValue)item).Value);
            return value;
        }
        public static string GetMemberValue(this XamlDomObject objectNode, XamlMember member)
        {
            string value = null;
            if (member != null)
            {
                XamlDomItem domItem = objectNode.GetMemberNode(member).Item;
                value = GetValue(domItem);
            }
            return value;
        }

        public static string GetMemberValue(this XamlDomObject objectNode, string memberName)
        {
            XamlDomMember domMember = objectNode.GetMemberNode(memberName);
            return objectNode.GetMemberValue(domMember.Member);
        }

        public static XamlDomItem GetPositionalParameter(this XamlDomObject objectNode, int index)
        {
            XamlDomItem item = null;
            XamlDomMember posParamsMember = null;
            foreach (XamlDomMember domMember in objectNode.MemberNodes)
            {
                if (domMember.Member.Name == "_PositionalParameters")
                {
                    posParamsMember = domMember;
                    break;
                }
            }

            if (posParamsMember != null && posParamsMember.Items.Count >= index + 1)
            {
                item = posParamsMember.Items[index];
            }
            return item;
        }



        public static XamlType GetXamlTypeFromTypeNameString(this XamlDomObject domObject, object dataType)
        {
            //instead we'll do it manually:
            string dataTypeStr = dataType as string;
            string prefix = string.Empty;
            string typeName = null;
            int colon = dataTypeStr.IndexOf(':');
            if (colon > -1)
            {
                prefix = dataTypeStr.Substring(0, colon);
                typeName = dataTypeStr.Substring(colon + 1);
            }
            else
            {
                typeName = dataTypeStr;
            }
            string ns = domObject.GetNamespace(prefix);
            XamlType xamlType = Program.SchemaContext.GetXamlType(new XamlTypeName(ns, typeName));
            if (xamlType == null)
            {
                xamlType = Program.SchemaContext.GetXamlType(
                    XamlDomUtilities.GetXamlTypeNameFromLocalAssembly(ns, typeName)
                );
            }
            return xamlType;
        }
        public static Type GetItemType(XamlDomObject objectNode)
        {
            Type result = null;
            AttachablePropertyServices.TryGetProperty(objectNode, ami, out result);
            return result;
        }
        public static AttachableMemberIdentifier ami = new AttachableMemberIdentifier(typeof(XamlDomUtilities), "ItemType");
        public static void SetItemType(XamlDomObject objectNode, Type type)
        {
            AttachablePropertyServices.SetProperty(objectNode, ami, type);
        }

        public static void EnsureItemType(this XamlDomObject objectNode)
        {
            bool hasItemsSource = objectNode.HasMember("ItemsSource");
            if (hasItemsSource)
            {
                XamlDomMember itemsSourceMember = objectNode.GetMemberNode("ItemsSource");
                XamlDomObject itemsSourceBinding = itemsSourceMember.Item as XamlDomObject;
                Binding subBinding = (Binding)Program.GetBindingFromDomObject(itemsSourceBinding);
                subBinding.DomObject = itemsSourceBinding;
                Type resolvedPathType = subBinding.ResolvedPathType;
                if (resolvedPathType != null)
                {
                    if (resolvedPathType.FullName == "System.Array")
                    {
                        resolvedPathType = typeof(object);
                    }
                    else
                    {
                        PropertyInfo itemProp = resolvedPathType.GetProperty("Item");
                        resolvedPathType = itemProp.PropertyType;
                    }
                    XamlDomUtilities.SetItemType(objectNode, resolvedPathType);
                }
            }
        }

        public static XamlDomObject GetNamescopeFor(this XamlDomObject objectNode)
        {
            XamlDomMember parentMember = objectNode.Parent;
            if (parentMember == null)
            {
                return objectNode;
            }
            else
            {
                XamlDomObject parentObject = parentMember.Parent;
                if (parentObject.Type.IsNameScope)
                {
                    return parentObject;
                }
                else
                {
                    return GetNamescopeFor(parentObject);
                }
            }
        }
        public static XamlDomObject FindNamedObject(this XamlDomObject nearestNamescope, string elementName)
        {
            string name = nearestNamescope.GetName();
            if (name == elementName)
            {
                return nearestNamescope;
            }

            foreach (XamlDomMember domMember in nearestNamescope.MemberNodes)
            {
                foreach (XamlDomItem domItem in domMember.Items)
                {
                    name = null;
                    XamlDomObject domObject = domItem as XamlDomObject;
                    if (domObject != null)
                    {
                        name = domObject.GetName();
                        if (name == elementName)
                        {
                            return domObject;
                        }
                        XamlDomObject subObject = FindNamedObject(domObject, elementName);
                        if (subObject != null)
                            return subObject;
                    }
                }
            }
            return null;
        }

        public static string GetName(this XamlDomObject domObject)
        {
            if (domObject != null)
            {
                XamlDomMember xNameMember = domObject.GetMemberNode(XamlLanguage.Name);
                XamlMember nameXM = domObject.Type.GetAliasedProperty(XamlLanguage.Name);
                if (nameXM != null && xNameMember == null)
                {
                    xNameMember = domObject.GetMemberNode(nameXM);
                }
                if (xNameMember != null)
                {
                    XamlDomItem nameItem = xNameMember.Item;
                    string name = ((string)((XamlDomValue)nameItem).Value);
                    return name;
                }
            }
            return null;
        }
        public static XamlDomObject FindParentObjectByType(this XamlDomObject objectNode, XamlType xamlType,
            bool outputPaths, bool acceptAssignableTypes)
        {
            XamlDomMember parentMember = objectNode.Parent;
            if (parentMember == null)
            {
                XamlType rootXamlType = objectNode.GetXamlTypeConsideringXClass();
                if (rootXamlType == xamlType)
                {
                    if (outputPaths)
                    {
                        Console.Write(rootXamlType.Name + ":");
                    }
                    return objectNode;
                }
                else
                {
                    if (outputPaths)
                    {
                        Console.Write(objectNode.Type.Name + ":");
                    }
                    return null;
                }
            }
            else
            {
                XamlDomObject parentObject = parentMember.Parent;
                if ((acceptAssignableTypes && parentObject.Type.CanAssignTo(xamlType))
                    || parentObject.Type == xamlType)
                {
                    if (outputPaths)
                    {
                        Console.Write(objectNode.Type.Name + ":");
                    }
                    return parentObject;
                }
                else
                {
                    XamlDomObject domResult = FindParentObjectByType(parentObject, xamlType, outputPaths, acceptAssignableTypes);
                    if (outputPaths)
                    {
                        Console.Write(objectNode.Type.Name + ":");
                    }
                    return domResult;
                }
            }
        }
        public static XamlType GetXamlTypeFromTypeNameWithPrefix(this XamlDomObject domObject, string AncestorType)
        {
            XamlTypeName typeName = GetXamlTypeName(AncestorType, domObject);
            return Program.SchemaContext.GetXamlType(typeName);
        }

        public static XamlTypeName GetXamlTypeName(string AncestorType, XamlDomObject domObject)
        {
            int colon = AncestorType.IndexOf(':');
            string prefix = "";
            string name = null;
            if (colon == -1)
            {
                name = AncestorType;
            }
            else
            {
                prefix = AncestorType.Substring(0, colon);
                name = AncestorType.Substring(colon + 1);
            }

            XamlTypeName xamlTypeName = new XamlTypeName();
            if (domObject != null)
            {
                xamlTypeName.Namespace = domObject.GetNamespace(prefix);
                if (xamlTypeName.Namespace.Contains("clr-namespace:") && !xamlTypeName.Namespace.Contains(";assembly="))
                {
                    //local type that isn't working
                    xamlTypeName.Namespace +=
                        ";assembly="
                        + Program.ProjectData.LocalAssembly.Assembly.FullName.Replace(" ", "");
                }
                xamlTypeName.Name = name;
            }
            return xamlTypeName;
        }
        public static XamlDomObject FindResource(this XamlDomObject domObject, object resourcekey)
        {
            XamlDomMember domMember = domObject.GetMemberNode("Resources");
            if (domMember != null)
            {
                XamlDomObject dictionary = (XamlDomObject)domMember.Items[0];
                XamlDomMember dictionaryItems = dictionary.MemberNodes[0];
                foreach (XamlDomObject dictionaryItem in dictionaryItems.Items)
                {
                    XamlDomMember keyMember = dictionaryItem.GetMemberNode(XamlLanguage.Key);
                    string key = null;
                    if (keyMember.Item is XamlDomValue)
                    {
                        key = dictionaryItem.GetMemberValue(XamlLanguage.Key);
                    }
                    else
                    {
                        //TODO: XamlTypeOf?
                        key = keyMember.GetMemberValueOfTypeProperty();
                    }

                    if (key == (string)resourcekey)
                    {
                        return dictionaryItem;
                    }
                }
            }

            //resources member wasn't found or resourceKey wasn't found
            if (domObject.Parent != null)
                return domObject.Parent.Parent.FindResource(resourcekey);
            else
                return null;
        }

        public static XamlTypeName GetXamlTypeNameFromLocalAssembly(string clrNamespace, string typeName)
        {
            string xmlnsString =
                clrNamespace
                + ";assembly="
                + Program.ProjectData.LocalAssembly.Assembly.FullName.Replace(" ", "");
            XamlTypeName xamlTypeName = new XamlTypeName(xmlnsString, typeName);
            return xamlTypeName;
        }

        public static XamlType GetXamlTypeConsideringXClass(this XamlDomObject domObject)
        {
            if (domObject.Parent == null)
            {
                //check for x:Class and use that type as the TypeContext.
                string classString = null;
                if (domObject.HasMember(XamlLanguage.Class))
                {
                    classString = domObject.GetMemberValue(XamlLanguage.Class);
                    if (classString != null)
                    {
                        int lastPeriod = classString.LastIndexOf('.');
                        //TODO: what about a string with no period. likely broken.
                        string clrNamespace = "clr-namespace:" + classString.Substring(0, lastPeriod);
                        string typeName = classString.Substring(lastPeriod + 1);
                        XamlTypeName xamlTypeName = XamlDomUtilities.GetXamlTypeNameFromLocalAssembly(clrNamespace, typeName);
                        return Program.SchemaContext.GetXamlType(xamlTypeName);
                    }
                }
            }
            return domObject.Type;
        }

    }
}
